import random
import operator
import os
# 修复：添加 time 库用于短暂暂停，优化体验
import time
from typing import List, Dict, TypedDict, Annotated, Literal

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langgraph.graph import StateGraph, END
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

# ---------------- 配置区域 ----------------
# 建议使用 GPT-4o，因为它的逻辑推理和防作弊判定能力比 3.5 强很多
# 请确保你的 .env 文件中配置了正确的环境变量
try:
    llm = ChatOpenAI(
        model=os.getenv("LONGMAO_MODEL"),
        base_url=os.getenv("LONGMAO_BASE_URL"),
        api_key=os.getenv("LONGMAO_API_KEY"),
        temperature=0.7
    )
except Exception as e:
    print(f"Error initializing LLM: {e}")
    print("Please check your .env file environment variables.")
    exit(1)

# 真人玩家的名字
HUMAN_NAME = "我(真人)"


# ---------------- 1. 定义游戏状态 ----------------
class GameState(TypedDict):
    # --- 基础信息 ---
    players: List[str]  # 玩家列表
    player_words: Dict[str, str]  # 每个人的词
    spy_player: str  # 谁是卧底

    # --- 游戏进程 ---
    history: Annotated[List[str], operator.add]  # 聊天记录
    current_turn_idx: int  # 当前轮到谁发言的索引
    round_count: int  # 第几轮辩论

    # --- 状态标志 ---
    votes: Dict[str, str]  # 投票箱
    game_result: str  # 游戏结果文本 (有值则游戏结束)
    cheater_detected: str  # 如果有人撒谎作弊，记录名字


# ---------------- 2. 核心节点逻辑 ----------------

def init_game_node(state: GameState):
    """上帝节点：发牌"""
    print("\n" + "=" * 40)
    print("🎮 游戏开始！正在分配词汇...")

    # 词库：(平民词, 卧底词)
    word_pairs = [
        ("苹果", "梨"), ("微信", "QQ"), ("麦当劳", "肯德基"),
        ("成吉思汗", "努尔哈赤"), ("蜘蛛侠", "蝙蝠侠"), ("程序员", "黑客")
    ]
    common_word, spy_word = random.choice(word_pairs)

    # 定义玩家 (1个真人 + 2个AI)
    players = [HUMAN_NAME, "AI_1号", "AI_2号"]
    # 打乱顺序，这样你就不知道自己是几号位
    random.shuffle(players)

    spy = random.choice(players)

    allocation = {}
    for p in players:
        allocation[p] = spy_word if p == spy else common_word

    # 在控制台悄悄告诉真人他的词
    human_word = allocation[HUMAN_NAME]
    identity = '卧底' if spy == HUMAN_NAME else '平民'
    print(f"🤫 嘘！你的身份是【{identity}】")
    print(f"🔑 你的词是：【{human_word}】 (切记：描述时不能直接包含这个词！)")
    print("-" * 40)
    print(f"👥 玩家发言顺序：{' -> '.join(players)}")
    print("=" * 40 + "\n")
    time.sleep(1)

    return {
        "players": players,
        "player_words": allocation,
        "spy_player": spy,
        "history": [],
        "current_turn_idx": 0,
        "round_count": 1,
        "votes": {},
        "cheater_detected": None
    }


def speaker_node(state: GameState):
    """发言节点：处理真人输入 或 AI生成"""
    current_player = state["players"][state["current_turn_idx"]]
    my_word = state["player_words"][current_player]
    round_num = state["round_count"]

    print(f"👉 轮到 [{current_player}] 发言...")

    # === 分支 A: 真人玩家 (增加了本地检查) ===
    if current_player == HUMAN_NAME:
        print(f"   (提示: 你的词是【{my_word}】，不能直接说出来哦)")
        if len(state['history']) > 0:
             print(f"   近期发言: {state['history'][-2:]}")

        # 使用 while 循环，直到输入合法为止
        while True:
            content = input(f"🎤 请输入你的描述 > ").strip()

            # 【第一道防线】：本地规则检查
            if not content:
                print("⚠️ 描述不能为空，请重新输入。")
                continue
            # 检查是否直接包含了关键词
            if my_word in content:
                print(f"🚫 犯规警告！你的描述中直接包含了关键词【{my_word}】。")
                print("请重新换一种说法，描述它的特征，但不要说出这几个字。")
                continue

            # 输入合法，跳出循环
            break

        msg = f"[{current_player}]: {content}"

    # === 分支 B: AI 玩家 ===
    else:
        # AI 的 Prompt (强调了不能直接说出)
        prompt = f"""
        你是 {current_player}，正在玩《谁是卧底》。你的词是【{my_word}】。
        现在是第 {round_num} 轮描述。

        历史发言：
        {state['history']}

        请用一句话描述你的词。
        **重要规则**：
        1. 严禁在描述中直接包含【{my_word}】这几个字，否则会被直接淘汰。
        2. 描述必须符合【{my_word}】的客观事实（严禁撒谎！比如你是梨，绝不能说是红色的）。
        3. 稍微模糊一点，不要让卧底猜到，也不要太模糊被当成卧底。
        """
        try:
            response = llm.invoke([HumanMessage(content=prompt)])
            content = response.content.replace(my_word, "***") # 简单防止AI偶尔抽风
            msg = f"[{current_player}]: {content}"
            print(f"🤖 {msg}")
            time.sleep(0.5) # 稍微停顿，让对话看起来自然点
        except Exception as e:
            msg = f"[{current_player}]: (AI掉线了...跳过)"
            print(f"❌ AI生成失败: {e}")

    return {
        "history": [msg],
        # 注意：这里暂不增加 current_turn_idx，因为还要过“裁判节点”检查
    }


def fact_check_node(state: GameState):
    """裁判节点：防作弊检查（双重检查：撒谎+爆词）"""
    current_player = state["players"][state["current_turn_idx"]]
    last_msg = state["history"][-1]  # 刚刚说的话
    real_word = state["player_words"][current_player]

    # 提取纯文本内容，去掉 [Player]: 前缀
    if ":" in last_msg:
        spoken_text = last_msg.split(":", 1)[1].strip()
    else:
        spoken_text = last_msg

    # 【第二道防线】：LLM 裁判检查
    # 更新了 Prompt，明确将“直接爆词”定义为违规
    prompt = f"""
    请作为《谁是卧底》游戏的公正裁判。
    玩家词汇：【{real_word}】
    玩家描述："{spoken_text}"

    请判断玩家的描述是否违规。以下两种情况均视为【违规(FAIL)】：
    1. 【事实性错误/撒谎】：为了伪装身份，描述了【{real_word}】完全不具备的特征（例如词是梨，却说它是红色的）。
    2. 【直接爆词/过于直白】：描述中直接包含了词汇本身，或者描述过于直白（例如词是苹果，直接说“我的词是苹果”），失去了游戏的意义。

    如果只是一般的模糊描述（例如说它是水果，这在游戏规则允许范围内），请判定为【通过(PASS)】。

    只需回答：PASS 或 FAIL
    """

    try:
        # 如果是真人，虽然也可以查，但为了游戏体验通常信任真人，这里演示一视同仁
        check_res = llm.invoke([HumanMessage(content=prompt)]).content.strip().upper()

        if "FAIL" in check_res:
            reason = "描述不符事实或直接爆词"
            print(f"\n🚨 裁判红牌！[{current_player}] 严重违规（{reason}）！直接淘汰！\n")
            return {"cheater_detected": current_player}

    except Exception as e:
        print(f"⚠️ 裁判连接失败，暂且放过: {e}")

    # 检查通过，轮次 + 1
    return {"current_turn_idx": state["current_turn_idx"] + 1}


def voting_node(state: GameState):
    """投票节点"""
    print(f"\n🗳️ === 第 {state['round_count']} 轮描述结束，开始投票 ===")
    print("-" * 30)
    players = state["players"]
    history_str = "\n".join(state["history"])
    new_votes = {}

    for p in players:
        # === 真人投票 ===
        if p == HUMAN_NAME:
            print("💀 请根据上面的历史发言，找出卧底。")
            print(f"   候选人: {players}")
            while True:
                vote_target = input(f"🗳️ 你投给谁? (输入名字的关键词) > ").strip()
                # 简单的模糊匹配修正
                found = False
                for valid in players:
                    if vote_target and (valid in vote_target or vote_target in valid):
                        vote_target = valid
                        found = True
                        break
                if found:
                    break
                print("❌ 找不到该玩家，请重新输入正确的名字。")
            print(f"✅ 你投给了: {vote_target}")


        # === AI 投票 ===
        else:
            my_word = state["player_words"][p]
            prompt = f"""
            你是 {p}，你的词是【{my_word}】。
            历史记录：
            {history_str}

            请在 {players} 中选出你认为是卧底的人。
            只输出名字，不要输出其他任何内容。
            """
            try:
                res = llm.invoke([HumanMessage(content=prompt)])
                vote_target = res.content.strip()
                # 清洗一下数据，确保投的是有效玩家
                found = False
                for valid in players:
                    if valid in vote_target:
                        vote_target = valid
                        found = True
                        break
                if not found:
                    # 如果AI瞎投，随机投一个非自己的
                    others = [pl for pl in players if pl != p]
                    vote_target = random.choice(others) if others else p

                print(f"   🤖 [{p}] 投给了 -> {vote_target}")
            except:
                 print(f"   🤖 [{p}] 弃票了")
                 vote_target = "弃票"

        new_votes[p] = vote_target
    print("-" * 30)

    return {"votes": new_votes}


def judge_node(state: GameState):
    """判决节点"""
    # 1. 优先检查有没有作弊被抓的
    if state.get("cheater_detected"):
        cheater = state["cheater_detected"]
        spy = state["spy_player"]
        if cheater == spy:
            return {"game_result": f"卧底 [{cheater}] 因为违规描述(撒谎/爆词)，被裁判直接罚下！平民胜利！🎉"}
        else:
            return {"game_result": f"平民 [{cheater}] 因为违规描述被罚下！但这局算平民输！😈"}

    # 2. 正常计票
    votes = state["votes"]
    counts = {}
    for target in votes.values():
        if target != "弃票":
            counts[target] = counts.get(target, 0) + 1

    print(f"📊 最终票数统计: {counts}")

    # 如果没人投票
    if not counts:
         print("⚖️ 无人投票，进入下一轮！\n")
         return {
            "round_count": state["round_count"] + 1,
            "current_turn_idx": 0,
            "history": [f"--- 第 {state['round_count']} 轮无人有效投票，继续游戏 ---"]
        }

    # 排序
    sorted_votes = sorted(counts.items(), key=lambda x: x[1], reverse=True)
    top_votes = sorted_votes[0][1]
    # 找出所有最高票的玩家
    top_players = [p for p, v in sorted_votes if v == top_votes]

    # 3. 平票检测 (Tie) - 如果最高票不止一个人
    if len(top_players) > 1:
        print(f"⚖️ 平票！玩家 {top_players} 票数相同，无人出局，进入下一轮描述！\n")
        return {
            "round_count": state["round_count"] + 1,
            "current_turn_idx": 0,
            "history": [f"--- 第 {state['round_count']} 轮平票({top_players})，继续游戏 ---"]
        }

    # 4. 淘汰结算
    out_player = top_players[0]
    print(f"\n❌ 经过公投，[{out_player}] 被淘汰出局！")

    spy = state["spy_player"]
    print(f"🔑 真相揭晓：卧底是 [{spy}]，词是【{state['player_words'][spy]}】。")
    print(f"   平民词是【{state['player_words'][HUMAN_NAME] if spy != HUMAN_NAME else state['player_words']['AI_1号']}】")

    if out_player == spy:
        result = f"\n🎉 恭喜！成功抓出卧底！平民阵营胜利！"
    else:
        # 简单逻辑：3人局只要投错平民，卧底就赢了
        result = f"\n😈 很遗憾，平民 [{out_player}] 被误杀，卧底获得胜利！"

    return {"game_result": result}


# ---------------- 3. 构建图与路由 ----------------

def turn_router(state: GameState):
    """决定下一个是谁说话，还是去投票"""
    # 如果有人作弊，直接去判决
    if state.get("cheater_detected"):
        return "judge"

    if state["current_turn_idx"] < len(state["players"]):
        return "speak"
    return "vote"


def game_router(state: GameState):
    """决定游戏是否结束"""
    if state.get("game_result"):
        return "end"
    return "continue"


workflow = StateGraph(GameState)

# 添加节点
workflow.add_node("Init", init_game_node)
workflow.add_node("Speaker", speaker_node)
workflow.add_node("FactCheck", fact_check_node)  # 裁判
workflow.add_node("Voting", voting_node)
workflow.add_node("Judge", judge_node)

# 编排流程
workflow.set_entry_point("Init")
workflow.add_edge("Init", "Speaker")

# Speaker 说完 -> 裁判检查
workflow.add_edge("Speaker", "FactCheck")

# 裁判检查完 -> 路由（通过则下一个，没通过则直接判决）
workflow.add_conditional_edges(
    "FactCheck",
    turn_router,
    {
        "speak": "Speaker",  # 还没说完一圈，下个人说
        "vote": "Voting",  # 说完一圈了，去投票
        "judge": "Judge"  # 有人作弊，直接去判决
    }
)

workflow.add_edge("Voting", "Judge")

workflow.add_conditional_edges(
    "Judge",
    game_router,
    {
        "continue": "Speaker",  # 平票，回到发言环节
        "end": END
    }
)

app = workflow.compile()

# ---------------- 4. 启动游戏 ----------------
if __name__ == "__main__":
    try:
        # 使用 invoke 运行
        app.invoke({"players": []})
    except KeyboardInterrupt:
        print("\n🛑 游戏已手动终止。")
    except Exception as e:
        print(f"\n❌ 游戏发生错误: {e}")
        # import traceback
        # traceback.print_exc()